
fn base(){
    let i = 3; // liftime for 'i' starts
    {
        let borrow1 = &i; // borrow1 lifetime starts
        println!("borrow1: {}", borrow1);
    }// borrow1 ends

    {
        let borrow2 = &i;// borrow2 lifetime starts
        println!("borrow2: {}", borrow2);
    }// borrow2 ends
}// lifetime ends



//NOTE: measn print_refs cannot exceeds that of either 'a or 'b
//means: 
//  0. print_refs have two lifetime parameter 'a and 'b;
//      >lifetime 'a is valid in calling print_refs; 
//      >lifetime 'b is valid in calling print_refs
//
//  1. print_refs lifetime fewer than 'a and 'b 
fn print_refs<'a, 'b>(x: &'a i32, y: &'b i32) {
    println!("x is {} and y is {}", x, y);
}

/*
 * 1> failed_borrow has one lifetime parameter 'a 
 * 2> _y is the 'a scope ref; 
 * 3> but _x is scope is less than failed_borrow
 * 4>. so  compile failed
fn failed_borrow<'a>(){
    let _x = 12;

    let _y: &'a i32 = &_x;
}*/

fn explicit_annotation(){
    let (four, nine) = (4, 9);
    print_refs(&four, &nine);
    //failed_borrow();
}


// function signatures [with lifetimes] have a few constraints: 
//  - any reference must have an anotated lifetime
//  - any reference being returned must have the same lifetime as an input or the static 

fn print_one<'a>(x: &'a i32){
    println!("print_one : x is {}", x);
}
fn add_one<'a>(x: &'a mut i32){
    *x += 1;
}
fn print_multi<'a, 'b>(x: &'a i32, y: &'b i32) {
    println!("print_multi: x is {}, y is {}", x, y);
}

// pass_x lifetime <= 'a & 'b; and returning ref lifetime equals to 'a
fn pass_x<'a, 'b>(x: &'a i32, _: &'b i32) -> &'a i32 { x }

fn functions(){
    let x = 7;
    let y = 9;
    print_one(&x);
    print_multi(&x, &y);

    let z = pass_x(&x, &y);
    print_one(z);

    let mut t = 3;
    add_one(&mut t);
    print_one(&t);
}

// methods similar to functions
struct Owner (i32);
impl Owner {
    fn add_one<'a>(&'a mut self) { self.0 += 1; }
    fn print<'a>(&'a self) {
        println!("print: {}", self.0);
    }
}

fn methods(){
    let mut owner = Owner(18);
    owner.add_one();
    owner.print();
}


//structs are also similar to functions
// a type Borrowed which hourses a reference to an 'i32'
// The ref to i32 must outlive Borrowed
#[derive(Debug)]
struct Borrowed<'a>(&'a i32);

//both ref must outlive NamedBorrowed
#[derive(Debug)]
struct NamedBorrowed<'a>{
    x: &'a i32,
    y: &'a i32,
}

// enum which is either an i32 or a reference to one i32 val
#[derive(Debug)]
enum Either<'a> {
    Num(i32),
    Ref(&'a i32),
}


fn structs(){
    let x = 18;
    let y = 15;
    let single = Borrowed(&x);
    println!("x is borrowed in {:?}", single);

    let double = NamedBorrowed { x: &x, y: &y };
    println!("x and y are borrowed in {:?}, x: {}, y: {}", double, double.x, double.y);

    let reference = Either::Ref(&x);
    println!("x is borrowed in {:?}", reference);

    let number = Either::Num(y);
    println!("y is not borrowed in {:?}", number);
}

//lifetime in trait methods are similar to functions;
// NOTE: impl may have annotation of lifetimes too.

#[derive(Debug)]
struct BorrowedStruct<'a> {
    x: &'a i32,
}
impl<'a> Default for BorrowedStruct<'a> {
    fn default() -> Self{
        Self {
            x: &10,
        }
    }
}

fn traits(){
    let b: BorrowedStruct = Default::default();
    println!("b is {:?}, struct-x:{}", b, b.x);
}

// generic type can be bounded,  lifetimes(trade as generic) use bounds as well.
// The : character has slightly different meaning here, but + is the same;
//  1. T: 'a => All ref in T must outlive lifetime 'a
//  2. T: Trait + 'a: Type T must implement trait Trait and all ref in T must outlive 'a.


use std::fmt::Debug;
// Ref has lifetime 'a;
// and All generic type T 's ref must outlive 'a
#[derive(Debug)]
struct Ref<'a, T: 'a>(&'a T);
// 1. Ref contains a ref to a generic type 'T' 
// 2. T has an unknown lifetime 'a; 
// 3. T is bounded such that any ref in T must outlive 'a.
// additionally, the lifetime of Ref may not exceed 'a

// print generic type T; T is bounded Debug Trait
fn print<T>(t: T) where T: Debug {
    println!("print: t is {:?}", t);
}

//print_ref : 'a & T ;
// T is impl Debug, and T ref must outlive 'a
fn print_ref<'a, T>(t: &'a T) where T: Debug + 'a {
    println!("print_ref: t is {:?}", t);
}

fn bounds(){
    let x = 7;
    let ref_x = Ref(&x);

    print_ref(&ref_x); //  use ref first 
    print(ref_x); // and the moved here ==> ref_x is impl Debug trait
    
    print_ref(&x);
}


// dependence
fn multiply<'a>(first: &'a i32, second: &'a i32) -> i32{
    first * second
}
// 'a: 'b => 'a is at least as long as 'b; 
// means a dependent to 'b ; lifetime of 'a >= lifetime 'b
// so return 'b ref can return 'a parameter
fn choose_first<'a: 'b, 'b>(first: &'a i32, _: &'b i32) -> &'b i32 {
    first
}

fn coercion(){
    let first = 2; 
    {
        let second = 3;
        println!("The product is {}", multiply(&first, &second)); //'a -> second
        println!("{} is the first", choose_first(&first, &second));
    }
}

//static base : only two situations for 'static 
// let s: &'static str = "hello world";  // var declare 
// fn generic<T>(x: T) where T: 'static {} // generic T bound


//NOTE: two ways to make var with 'static
// - Make a constant with the static declaration
// - Make a string literal which hash type: &'static str.
static NUM: i32 = 18;

// coerce the lifetime from 'static to 'a
fn coerce_static<'a>(_: &'a i32) -> &'a i32{
    &NUM
}
fn static_ref_lifetime(){
    {
        let static_string = "I'm in read-only memory";
        println!("static_string: {}", static_string);
    }
    {
        let lifetime_num = 9;
        let coerced_static = coerce_static(&lifetime_num);
        println!("coerced_static: {}", coerced_static);
    }
    println!("NUM: {} stays accessible!", NUM);
}

//NOTE: any owned data always passes a 'static lifetime bound;
//  let i = 5;  //->  i is 'static , because of i is owned the data
//use std::fmt::Debug;
fn print_it(input: impl Debug + 'static) {
    println!("static value passed in is: {:?}", input);
}
fn static_trait_bound(){
    let i = 5;
    print_it(i); // i is owned and contains no reference, thus it's 'static

    //print_it(&i); // ref i is non-static //error
}
fn static_lifetime(){
    static_ref_lifetime();
    static_trait_bound();
}

// lifetime of elided_input parameter is inferred by the compiler
fn elided_input(x: &i32){ 
    println!("elided_input: {}", x);
}
fn annotated_input<'a>(x: &'a i32) {
    println!("annotated_input: {}", x);
}
// similarly, lifetime of elided_pass parameter is inferred by compiler
fn elided_pass(x: &i32) -> &i32 { x }
fn annotated_pass<'a>(x: &'a i32) -> &'a i32 { x }

fn elision(){
    let x = 3;
    elided_input(&x);
    annotated_input(&x);

    println!("elided_pass: {}", elided_pass(&x));
    println!("annotated_pass: {}", annotated_pass(&x));
}

pub fn main(){
    base();
    explicit_annotation();
    functions();
    methods();
    structs();
    traits();
    bounds();
    coercion();
    static_lifetime();
    elision();
}

